﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Forms;


namespace ConsoleApplication1
{
    class SimData
    {
         
         public Dictionary<String, Machine> mymachines = new Dictionary<String, Machine>();
         public Dictionary<String, MaterialProd> mymaterialsprod = new Dictionary<String, MaterialProd>();
         public Dictionary<int, Epoch> myepochs = new Dictionary<int, Epoch>();
         public Dictionary<String, Materials> mymaterials = new Dictionary<String, Materials>();
         public Dictionary<String, Dictionary<String, float>> mybommat;
         public Dictionary<String, Dictionary<String, float>> mybomcom;
         public Dictionary<int, int> myleadtimes = new Dictionary<int, int>();
         public Dictionary<String, Dictionary<String, Dictionary<String, Setup>>> mysetup = new Dictionary<string, Dictionary<string, Dictionary<string, Setup>>>();
         public Dictionary<String, float> myparameters = new Dictionary<String,float>();

      
         String materials_path;
         String matprod_path;
         String machine_path;
         String epochs_path;
         String bom_path;
         String leadtimes_path;
         String parameters_path;
         String setup_path;

        //Amount of MAX Design Variables in the system. Transforms the time step.

        double bincountunit;
        int stagelocation = 0;
        int machinenamelocation = 1;
        int epochlocation = 2;
        int nethourslocation = 3;
        public int setuprows =0;
        int materiallocation = 0;
        int pricelocation = 1;
        int targetlocation = 2;
        int holdingcostlocation = 3;
        int backordercostlocation = 4;


        public SimData(String rootpath, double bincount )
        {
            this.bincountunit = (double)bincount;
            materials_path = rootpath +  "\\PPS_MATERIALS.csv";
            matprod_path = rootpath + "\\PPS_MATERIALPROD.csv";
            machine_path = rootpath + "\\PPS_MACHINES.csv";
            epochs_path = rootpath + "\\PPS_DEMAND.csv";
            bom_path = rootpath + "\\PPS_BOM.csv";
            leadtimes_path = rootpath + "\\PPS_LEADTIMES.csv";
            parameters_path = rootpath + "\\PPS_PARAMETERS.csv";
            setup_path = rootpath + "\\PPS_SETUP.csv";
            loadAll();
   
        }


        public String describeYourself()
        {
            String output = "I am the SIM DATA, I loaded this much:\n"; 
            foreach (String mp in mymaterialsprod.Keys)
            {
                output += "MATPROD:"+mp +"\n";
                Machine mach = mymaterialsprod[mp].machine;
                Materials mat = mymaterialsprod[mp].material;
                output += mach.describeYourself();
                output += "\n";
                output += mat.describeYourself();
                output += "\n";
            }
            output += "end of SIM DATA";
            return output;
        }
        public void loadAll()
        {
         
           loadParameters(parameters_path);
           loadMaterials(materials_path);          
           loadMachines(machine_path);
           loadMaterialProd(matprod_path);          
           loadEpochs(epochs_path);
           loadBom(bom_path);
           loadleadtimes(leadtimes_path);           
           loadSetup(setup_path);
           loadhorizon();
        }
      

        private int getMachinesCount(String path)
        {
            int machcount = 0;
            List<string[]> values = parseCSV(path);
            string previousMachine = "";
            for (int i = 1; i < values.Count; i++)
            {
                String[] parameters_values = values.ElementAt(i);
                string curmach = parameters_values[1];
                if (!curmach.Equals(previousMachine))
                {
                    previousMachine = curmach;
                    machcount++;
                }
            }
            return machcount;
            
        }


        public float horizon;
        public float invdeficitcost;
        public float stages;
        public int alpha;

        // Has to be loaded after MachCount 
        private void loadParameters(String path)
        {
            List<string[]> values = parseCSV(path);
            int i=1;
            String parameterid;
            float parametervalue;

            for (i=1; i <values.Count; i++)
            {
                String[] parameters_values = values.ElementAt(i);
                parameterid= parameters_values[0];
                parametervalue= float.Parse(parameters_values[1]);
                
                myparameters.Add(parameterid,parametervalue);
            }

            int k = 0;
            float horizonraw;
            int bincount = 0;

            horizonraw = myparameters["Horizon"];        
           
                for (int j = 0; j < horizonraw; j++)
                {
                    k = k + 1;                
                }

            bincount = k * getMachinesCount(machine_path);
           

            //si crecimiento lineal
            double kcocient=0;
            kcocient= bincount/bincountunit;
            alpha =Convert.ToInt32(Math.Ceiling(kcocient)); 
                
            //Para donde redondeamos? q pasa con final de epocas y final total?
            //horizon = Math.Ceiling(horizonraw / alpha);
            //horizon = 12;

            invdeficitcost = myparameters["InvDeficitCost"];
            stages = myparameters["Stages"];

            Console.WriteLine("Horizonraw = " + horizonraw);
            Console.WriteLine("Variablesraw = " + bincount);
            Console.WriteLine("Time Step = " + alpha + " hours");
            Console.WriteLine("InvDefictCost = " + invdeficitcost);
            Console.WriteLine("Stages = " + stages);
            
        }

        private void loadMaterials(String path)
        {
            List<string[]> values = parseCSV(path);
            int i = 1;
            Materials mymaterial;
            string material_name;
            float price;
            float target;
            float holdingcost;
            float backordercost;

            for (i = 1; i < values.Count; i++)
            {
                String[] material_values = values.ElementAt(i);
                material_name = material_values[materiallocation];
                price = float.Parse(material_values[pricelocation]);
                target = float.Parse(material_values[targetlocation]);
                holdingcost = float.Parse(material_values[holdingcostlocation])*alpha;
                backordercost = float.Parse(material_values[backordercostlocation])*alpha;
                mymaterial = new Materials(material_name, price, target, holdingcost, backordercost);
                mymaterials.Add(material_name,mymaterial);
            }

        }

        private void loadMachines(String path)
        {
            List<string[]> values = parseCSV(path);
            int i = 1;
            Machine mymachine;
      
            // ojo q las nethours las nombra como float
            Dictionary<String, Dictionary<int, float>> prevalues = new Dictionary<string, Dictionary<int, float>>();
            Dictionary<String, int> prestages = new Dictionary<string,int>(); 
            for (i = 1; i < values.Count; i++)
            {
                int stage;
                string machine_name;
                int epoch;
                float nethours;
                String[] mach_values = values.ElementAt(i);
                machine_name = mach_values[machinenamelocation];
                if (!prevalues.ContainsKey(machine_name))
                    prevalues.Add(machine_name, new Dictionary<int, float>());

                Dictionary<int, float> nethoursdict = prevalues[machine_name];

                stage = int.Parse(mach_values[stagelocation]);
                
                epoch = int.Parse(mach_values[epochlocation]);
                nethours = (float.Parse(mach_values[nethourslocation]))/alpha;

                nethoursdict.Add(epoch, nethours);

                if (!prestages.ContainsKey(machine_name))
                    prestages.Add(machine_name, stage);

            }

            foreach (String key in prevalues.Keys)
            {
                int tempstage = prestages[key];
                Dictionary<int, float> nethours = prevalues[key];

                mymachine = new Machine(key, tempstage, nethours);
                mymachines.Add(key,mymachine);
            }
        }

        //Ojo que piden la duration como float. Pero nuestro modelo no puede.
        private void loadEpochs(string path)
        {
            List<string[]> values = parseCSV(path);
            int i;
            for (i = 1; i < values.Count; i++)
            {
                String[] demandtokens = values.ElementAt(i);
                string matkey = demandtokens[0];
                int epoch = int.Parse(demandtokens[1]);
                float duration = float.Parse(demandtokens[2])/alpha;
                float demand = (float.Parse(demandtokens[3]))*alpha;
 
                int start = 0;

                if(myepochs.ContainsKey(epoch -1))
                    start = myepochs[epoch-1].start+(int)Math.Round(myepochs[epoch-1].duration);
                if(!myepochs.ContainsKey(epoch))
                    myepochs.Add(epoch, new Epoch(epoch,duration,start));

                Materials material = mymaterials[matkey];

                try
                {
                    material.demand.Add(epoch, demand);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message + matkey);
                    Console.WriteLine("For the same material there are two demands. The second value was discarded");

                    //if (material.demand.ContainsKey(matkey))
                    //{
                    //    Console.WriteLine("MaterialProd " + key + " refers to non existent machine in PPS_MACHINE.cvs " + prod + "! Discarding this data.");
                    //}
                    //if (!mymaterials.ContainsKey(material))
                    //{
                    //    Console.WriteLine("MaterialProd " + key + " refers to non existent material in PPS_MATERIALS.cvs " + material + "! Discarding this data.");
                    //}
                }

            }
        }

        // Calculation of Horizon as the addition of the durations of epochs. 
        // We can´t get the value from parameters table because the durations are rounded up.
        private void loadhorizon()
        {
            foreach (Epoch epo in myepochs.Values)
            {
                horizon += epo.duration;
            }
         Console.WriteLine("Horizon = " + horizon + " time steps");
         }
       
        private void loadBom(string path)
        {
            List<string[]> values = parseCSV(path);
            String material;
            String component;
            float factor;
            int i = 1;

            //Doing this to optimize the read on these dictionaries and reduce collisions.

            mybomcom = new Dictionary<string, Dictionary<string, float>>(values.Count);
            mybommat = new Dictionary<string, Dictionary<string, float>>(values.Count);
            
            for (i = 1; i < values.Count; i++)
            {
                String[] bomvalues = values.ElementAt(i);
                material = bomvalues[0];
                component = bomvalues[1];
                factor = float.Parse(bomvalues[2]);

               if(!mybommat.ContainsKey(material))
                {
                     mybommat.Add(material, new Dictionary<String, float>() );
                }

                mybommat[material].Add(component,factor);


                if (!mybomcom.ContainsKey(component))
                {

                    mybomcom.Add(component, new Dictionary<String, float>());
                }

                mybomcom[component].Add(material, factor);
            }
        }

        private void    loadSetup(String path)
        {
            List<string[]> values = parseCSV(path);
            setuprows = values.Count;
            String machine;
            String materialfrom;
            String materialto;
            float setuptime;
            float setupcost;
            Setup setup;
            int i = 1;

            for (i = 1; i < values.Count; i++)
            {
                String[] setupvalues = values.ElementAt(i);
                machine = setupvalues[0];
                materialfrom = setupvalues[1];
                materialto = setupvalues[2];
                setuptime = float.Parse(setupvalues[3])/alpha;
                setupcost = float.Parse(setupvalues[4]);

                if (!mysetup.ContainsKey(machine))
                {
                    mysetup.Add(machine, new Dictionary<string, Dictionary<string, Setup>>());
                }

                if (!mysetup[machine].ContainsKey(materialfrom))
                {
                    mysetup[machine].Add(materialfrom, new Dictionary<string,Setup>());
                }
                
                setup = new Setup(setuptime, setupcost);

                if (!mysetup[machine][materialfrom].ContainsKey(materialto))
                {
                    mysetup[machine][materialfrom].Add(materialto, setup);
                }else
                { Console.WriteLine("There are two setup values for the same key fields!"); }
            }


            //Esta tirando error de memoria asi que voy a reducir el tamaño de las cosas
            //aqui, en vez de agregar 0 a todo, voy a ver si la clave existe en el optimize
            // si no existe, agrego un 0
            /*
            bool inconsistent = false;
            //Checking consistency
            foreach (Machine mach in mymachines.Values)
            {
                if (mach.materials.Count > 1)
                {
                    if (!mysetup.ContainsKey(mach.machine))
                    {
                        mysetup.Add(mach.machine, new Dictionary<string, Dictionary<string, Setup>>());
                        if (!inconsistent)
                            inconsistent = true;

                    }
                    foreach (String matkey in mach.materials.Keys)
                    {
                        if (!mysetup[mach.machine].ContainsKey(matkey))
                        {
                            mysetup[mach.machine].Add(matkey, new Dictionary<string, Setup>());
                            if (!inconsistent)
                                inconsistent = true;
                        }
                        foreach (String mattokey in mach.materials.Keys)
                        {
                            if (!mattokey.Equals(matkey)&& !mysetup[mach.machine][matkey].ContainsKey(mattokey))
                            {
                                mysetup[mach.machine][matkey].Add(mattokey, new Setup(0, 0));
                                if (!inconsistent)
                                    inconsistent = true;
 
                            }
                        }

                    }
                    

                }
             
            }
            if(inconsistent)
                Console.WriteLine("Setup Data is inconsistent, missing data added with setupcost 0");
             * */
                   
        }

        private void loadleadtimes(string path)
        {
            List<string[]> values = parseCSV(path);
            int StageIni;
            int Leadtime;
            int i = 1;

            for (i = 1; i < values.Count; i++)
            {
                String[] leadtimevalues = values.ElementAt(i);
                StageIni = int.Parse(leadtimevalues[0]);
                Leadtime = (int)((Math.Ceiling(float.Parse(leadtimevalues[2])))/alpha);

                myleadtimes.Add(StageIni, Leadtime);
            }

          }


        private void loadMaterialProd(string path)
        {
            List<string[]> values = parseCSV(path);
            String material;
            String prod;
            float rate;
            float cost;
            int i = 1;
            MaterialProd matprod;
            for (i = 1; i < values.Count; i++)
            {
                String[] matprodvalues = values.ElementAt(i);
                material = matprodvalues[1];
                prod = matprodvalues[0];
                rate = float.Parse(matprodvalues[2])*alpha;
                cost = float.Parse(matprodvalues[3]);
                String key = prod + material;

                try
                {
                    matprod = new MaterialProd(key, mymachines[prod], mymaterials[material], rate, cost);
                    mymaterialsprod.Add(key, matprod);
                    Materials materialobj = mymaterials[material];
                    mymachines[prod].materials.Add(material, materialobj);
                    Machine machineobj = mymachines[prod];
                    materialobj.machines.Add(prod, machineobj);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message + key);
                    if (!mymachines.ContainsKey(prod))
                    {
                        Console.WriteLine("MaterialProd " + key + " refers to non existent machine in PPS_MACHINE.cvs " + prod + "! Discarding this data.");
                    }
                    if (!mymaterials.ContainsKey(material))
                    {
                        Console.WriteLine("MaterialProd " + key + " refers to non existent material in PPS_MATERIALS.cvs " + material + "! Discarding this data.");
                    }
                }
            }
        }

        public MaterialProd getMaterialProd(Machine machine, Materials material)
        {
            String key = machine.machine + material.material;
            return mymaterialsprod[key];
        }

        public List<string[]> parseCSV(string path)
        {
            List<string[]> parsedData = new List<string[]>();

            try
            {
                using (StreamReader readFile = new StreamReader(path))
                {
                    string line;
                    string[] row;

                    while ((line = readFile.ReadLine()) != null)
                    {
                        row = line.Split(',');
                        parsedData.Add(row);
                    }
                }
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message);
                throw new Exception("Incorrect Data Folder");
            }

            return parsedData;
        }
    }
}
